package com.zhome.gateway.filter.route;

import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import com.zhome.gateway.constants.Constants;
import com.zhome.gateway.dto.ZhomeRouteDto;
import com.zhome.gateway.enums.FilterEnum;
import com.zhome.gateway.enums.GateWayCodeEnum;
import com.zhome.gateway.enums.ZhomeHttpEnum;
import com.zhome.gateway.filter.AbstractZhomeZuulFilter;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.springframework.cloud.netflix.zuul.filters.ProxyRequestHelper;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Enumeration;

/**
 * 路由功能
 * @author cuiyh9
 * @date 2020/5/9
 */
@Slf4j
public class HomeHostRoutingFilter extends AbstractZhomeZuulFilter {

    private CloseableHttpClient httpClient;

    private ProxyRequestHelper helper;

    private ZuulProperties.Host hostProperties;

    private boolean sslHostnameValidationEnabled;

    public HomeHostRoutingFilter (ProxyRequestHelper helper, ZuulProperties properties) {
        this.helper = helper;
        this.hostProperties = properties.getHost();
        this.sslHostnameValidationEnabled = properties.isSslHostnameValidationEnabled();
    }

    @PostConstruct
    private void initialize() {
        httpClient = buildHttpClient();
    }

    @Override
    public String filterType() {
        return Constants.ROUTE_KEY;
    }

    @Override
    public int filterOrder() {
        return FilterEnum.HomeHostRoutingFilterEnum.getFilterOrder();
    }

    @Override
    public boolean shouldFilter() {
        RequestContext ctx = RequestContext.getCurrentContext();
        ZhomeRouteDto zhomeRouteDto = (ZhomeRouteDto)ctx.get(Constants.RequestContextKey.CTX_X_ROUTEINFO);
        return ctx.getBoolean(Constants.RequestContextKey.CTX_X_SUCCESSOR_CAN_EXECUTE)
                && Constants.ServerType.HTTP.equals(zhomeRouteDto.getType());
    }

    @Override
    public Object run() throws ZuulException {
        try {
            RequestContext ctx = RequestContext.getCurrentContext();
            HttpServletRequest request = ctx.getRequest();

            ZhomeRouteDto zhomeRouteDto = (ZhomeRouteDto)ctx.get(Constants.RequestContextKey.CTX_X_ROUTEINFO);
            String method = zhomeRouteDto.getHttpMethod();
            ctx.set(Constants.RequestContextKey.CTX_X_REQ_START_TIME, System.currentTimeMillis());
            HttpResponse resp = null;
            if (ZhomeHttpEnum.isPostMethod(method)) {
                resp = doPost(ctx, request, zhomeRouteDto);
            }
            if (ZhomeHttpEnum.isGetMethod(method)) {
                resp = doGet(ctx, request, zhomeRouteDto);
            }
            setResponse(resp);
        } catch (Exception e) {
            log.error("路由出现异常!requestId:%s", requestId(), e);
            setFailResponse(GateWayCodeEnum.ENDPOINT_FAIL);
        }
        return null;
    }

    public HttpResponse doPost(RequestContext ctx, HttpServletRequest request, ZhomeRouteDto zhomeRouteDto) throws Exception {
        RequestConfig requestConfig = buildRequestConfig(zhomeRouteDto);
        HttpPost  httpPost = buildHttpPost(ctx, request, zhomeRouteDto, requestConfig);
        HttpResponse resp = httpClient.execute(httpPost);
        return resp;
    }

    public HttpResponse doGet(RequestContext ctx, HttpServletRequest request, ZhomeRouteDto zhomeRouteDto) throws Exception {
        RequestConfig requestConfig = buildRequestConfig(zhomeRouteDto);
        HttpGet httpGet = buildHttpGet(ctx, request, zhomeRouteDto, requestConfig);
        HttpResponse resp = httpClient.execute(httpGet);
        return resp;

    }

    protected void setResponse(HttpResponse response) throws  IOException {
        this.helper.setResponse(response.getStatusLine().getStatusCode(),
                response.getEntity() == null ? null : response.getEntity().getContent(),
                revertHeaders(response.getAllHeaders()));
    }


    private CloseableHttpClient buildHttpClient() {
        PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
        //将最大连接数增加到200
        cm.setMaxTotal(200);
        //将每个路由的默认最大连接数增加到20
        cm.setDefaultMaxPerRoute(20);
        CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(cm).build();
        return httpClient;
    }

    private RequestConfig buildRequestConfig(ZhomeRouteDto zhomeRouteDto) {
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(zhomeRouteDto.getConnectTimeout())
                .setSocketTimeout(zhomeRouteDto.getSocketTimeout())
                .build();
        return requestConfig;
    }

    private HttpPost buildHttpPost(RequestContext ctx, HttpServletRequest request,
                                   ZhomeRouteDto zhomeRouteDto, RequestConfig requestConfig ) {
        String fullUrl = (String)ctx.get(Constants.RequestContextKey.CTX_X_FULL_URL);
        HttpPost httpPost = new HttpPost(fullUrl);

        Enumeration<String> headerNames =  request.getHeaderNames();
        while (headerNames != null && headerNames.hasMoreElements()) {
            String headerName = headerNames.nextElement();
            httpPost.setHeader(headerName, request.getHeader(headerName));
        }
        httpPost.setHeader(Constants.RequestHeader.X_REQUEST_ID, requestId());

        String requestBody = (String)ctx.get(Constants.RequestContextKey.CTX_X_REQUEST_BODY);
        if (!StringUtils.isEmpty(requestBody)) {
            ContentType contentType = ContentType.create(zhomeRouteDto.getContentType(), StandardCharsets.UTF_8);
            StringEntity stringEntity = new StringEntity(requestBody, contentType);
            httpPost.setEntity(stringEntity);;
        }

        httpPost.setConfig(requestConfig);
        return httpPost;
    }

    private HttpGet buildHttpGet(RequestContext ctx, HttpServletRequest request,
                                 ZhomeRouteDto zhomeRouteDto, RequestConfig requestConfig ) {

        String fullUrl = (String)ctx.get(Constants.RequestContextKey.CTX_X_FULL_URL);
        HttpGet httpGet = new HttpGet(fullUrl);
        httpGet.setConfig(requestConfig);
        Enumeration<String> headerNames =  request.getHeaderNames();
        while (headerNames != null && headerNames.hasMoreElements()) {
            String headerName = headerNames.nextElement();
            if (!headerName.startsWith("X-")) {
                continue;
            }
            httpGet.setHeader(headerName, request.getHeader(headerName));
        }
        httpGet.setHeader(Constants.RequestHeader.X_REQUEST_ID, requestId());
        return httpGet;
    }

    private MultiValueMap<String, String> revertHeaders(Header[] headers) {
        MultiValueMap<String, String> map = new LinkedMultiValueMap<String, String>();
        for (Header header : headers) {
            String name = header.getName();
            if (!map.containsKey(name)) {
                map.put(name, new ArrayList<String>());
            }
            map.get(name).add(header.getValue());
        }
        return map;
    }
}
